Skip to content

feat(cli): prompt in a GUI instead of the console, plus a global approval policy#122

Draft
domenkozar wants to merge 5 commits into
mainfrom
feat/trusted-prompts
Draft

feat(cli): prompt in a GUI instead of the console, plus a global approval policy#122
domenkozar wants to merge 5 commits into
mainfrom
feat/trusted-prompts

Conversation

@domenkozar

@domenkozar domenkozar commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Trusted local prompts for value entry and secret release, so a CI job or coding agent can trigger secret operations without ever seeing the values, and cannot self-approve.

  • Mark a secret interactive = true (or secretspec set --ask) to type its value into a trusted dialog instead of stdin.
  • Set require_approval in [project] (true / false / "agents") to require human approval at that same trusted dialog before secretspec run injects secrets or secretspec get/import releases one.
  • Because the prompt is read on a channel the caller does not control, the triggering tool cannot forge or self-answer it.

Trusted channel: a built-in GUI

The CLI renders the prompt with a self-contained GUI dialog and no longer needs an external pinentry binary installed. The channel resolves as:

  1. Built-in egui-pinentry dialog (with the gui-prompt feature, enabled by cli) — a small window drawn with egui on the CPU (no GPU / OpenGL / Vulkan), that on X11 grabs the keyboard to resist snooping.
  2. A GnuPG pinentry binary when built without gui-prompt.
  3. A /dev/tty prompt when no display is available. A detected agent is refused any terminal-bound channel.

New crate: egui-pinentry

A self-contained passphrase + confirmation dialog crate (its own commit; publishable independently):

  • Pure-CPU render via softbuffer + egui_software_backend (no GPU crates in the tree).
  • X11 XGrabKeyboard, released via a Drop guard; no-op on Wayland/macOS/Windows.
  • Keystrokes go into a zeroize::Zeroizing buffer (never an egui TextEdit), so the secret is not retained in egui's widget/undo state; returned as a SecretString.
  • One event loop reused across dialogs (run_app_on_demand).
  • Headless UI tests with egui_kittest.

SDKs stay lean

secretspec-ffi, -py, and -node now depend on the library with default-features = false (+ the providers they resolve from), so gui-prompt and the ~78 GUI crates are never pulled into the language SDK cdylibs. Verified: building the ffi crate compiles egui-pinentry 0×, the CLI 1×.

Notes

  • 333 unit tests pass (trusted-prompt/approval paths use test hooks that bypass the real window).
  • An interactive secret (or set --ask) never reads a piped value: echo v | secretspec set KEY prompts rather than storing v. Pass it inline (secretspec set KEY "$V") or leave the secret non-interactive.

🤖 Generated with Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
secretspec e378150 Commit Preview URL

Branch Preview URL
Jul 08 2026, 10:57 PM

Add pinentry-based trusted value entry for `set` and `check`, and
require explicit human approval before releasing secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
domenkozar and others added 2 commits July 6, 2026 15:18
Add an `egui-pinentry` crate: a passphrase and confirmation dialog drawn
with egui rasterized on the CPU (no GPU, no OpenGL/Vulkan) via softbuffer,
with an X11 keyboard grab released via a Drop guard, keystrokes fed into a
zeroizing buffer (no egui TextEdit undo retention), and a `SecretString`
result. One event loop is reused across dialogs. The dialog layout is
covered by headless egui_kittest tests. devenv gains the windowing libs on
LD_LIBRARY_PATH so it runs inside the dev shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route trusted value entry and approval through the egui-pinentry crate
behind a `gui-prompt` feature (enabled by `cli`), so the CLI shows a
self-contained dialog and no longer needs an external pinentry binary.
Without the feature it falls back to a pinentry binary, and with no
display to a `/dev/tty` prompt.

Keep the GUI stack out of the language SDK cdylibs: secretspec-ffi, -py,
and -node now depend on the library without default features and re-enable
only the providers they resolve from, so `gui-prompt` (and egui) is never
pulled into those artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@domenkozar
domenkozar force-pushed the feat/trusted-prompts branch from 36b8d22 to d1144f4 Compare July 8, 2026 21:09
@domenkozar domenkozar changed the title feat(cli): trusted value entry and human approval for secrets feat(cli): auto-detected trusted prompts and a global approval policy Jul 8, 2026
@domenkozar
domenkozar force-pushed the feat/trusted-prompts branch from d1144f4 to c420c82 Compare July 8, 2026 21:19
@domenkozar domenkozar changed the title feat(cli): auto-detected trusted prompts and a global approval policy feat(cli): prompt in a GUI instead of the console, plus a global approval policy Jul 8, 2026
…oval policy

Drop the per-secret `interactive` flag and the `set --ask` flag. `set` and the
`check` fill loop now detect whether this machine can show a GUI prompt (a
display server plus a dialog to draw with: the built-in one, or a pinentry
binary without the `gui-prompt` feature) and prefer it over reading the console
whenever it can.

This forces a precedence choice the opt-in used to sidestep, since a non-terminal
stdin means both "a script is piping a value" and "an agent invoked me with a pipe
it controls". Resolved toward the security property: inline values win, then the
GUI prompt, then a terminal prompt, then a piped stdin. On a machine with a
display, `echo v | secretspec set KEY` now opens a dialog rather than storing `v`;
pass the value inline to seed a secret from a script.

When detection sees a display but the dialog cannot open on it and there is no
controlling terminal either, fail closed with an actionable message rather than
fall back to the caller-controlled stdin.

Also let `require_approval` be set once for every project, as
`[defaults].require_approval` in the user's config, so an operator can ask to be
prompted whenever an agent releases secrets without editing each `secretspec.toml`.
The project and user policies combine by taking whichever is stricter
(`false` < `"agents"` < `true`), making this a floor rather than a fallback: a
project cannot switch off the approval an operator configured for themselves, and
an operator cannot switch off the approval a project demands of every clone. A
plain fallback would have let any project's explicit `require_approval = false`,
the default value, silently disable the operator's safeguard. `secretspec config
show` prints the user-level policy.

Correct the docs to describe the built-in GUI dialog rather than an external
pinentry binary, and to note that `require_approval` gates `import` alongside
`run` and `get`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@domenkozar
domenkozar force-pushed the feat/trusted-prompts branch from c420c82 to 38dd77f Compare July 8, 2026 22:10
…ell evaluates

The LD_LIBRARY_PATH for egui-pinentry's X11/Wayland windowing stack referenced
wayland + xorg.* unconditionally. Those packages are Linux-only, so evaluating
the devenv shell on a darwin host aborted with "wayland is not available on the
requested hostPlatform", failing the macOS test job (which then cancelled the
Linux one). Guard the block with lib.optionalAttrs stdenv.isLinux; the libs are
only needed on Linux anyway, since macOS windowing is native Cocoa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@domenkozar
domenkozar marked this pull request as draft July 8, 2026 23:43
@domenkozar

Copy link
Copy Markdown
Member Author

Blocked on dsociative/pinentry-egui#1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant